package com.cgnpc.scp.filter;

import com.cgnpc.scp.common.utils.IOUtil;
import com.cgnpc.scp.common.utils.JacksonUtil;
import com.cgnpc.scp.common.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class CharSafeRequestWrapper extends HttpServletRequestWrapper {
    private static final Logger LOGGER = LoggerFactory.getLogger(CharSafeWithSqlFilter.class);

    /**
     * onlyCheckUnsafeChar;只检测不安全字符；true检测到不安全字符将返回错误信息，请求终止；false将替换不安全字符；
     */
    private boolean onlyCheckUnsafeChar = false;

    /**
     * hasUnsafeChar;值true包含不安全字符；
     */
    private boolean hasUnsafeChar = false;

    /**
     * needUsedServletInputStream;是否使用ServletInputSteam;<br/>
     * 一般来说ServletInputStream只能使用一次；<br/>
     */
    private boolean needUsedServletInputStream = false;

    private Map<String, String[]> parameterMap;
    private Hashtable<String, String[]> parameterMapWithTable;

    /**
     * requestBody<br/>
     * 该值可能null<br/>
     * 一般来说上传文件必定为null;<br/>
     */
    private String requestBody;

    /**
     * inputStream<br/>
     * 该值可能为null;<br/>
     * 一般来说上传文件必定为null;<br/>
     */
    private CharSafeServletInputStream inputStream;

    public CharSafeRequestWrapper(HttpServletRequest request, boolean onlyCheckUnsafeChar) {
        super(request);
        final String logInfo = "[CharSafeRequestWrapper_UUID_" + UUID.randomUUID().toString() + "]";
        this.onlyCheckUnsafeChar = onlyCheckUnsafeChar;
        this.needUsedServletInputStream = this.needUsedServletInputStream(request);

        if (this.onlyCheckUnsafeChar) {
            onlyCheckUnsafeChar(logInfo, request);
        } else {
            replaceUnsafeChar(logInfo, request);
        }
    }

    /**
     * 是否包含不安全的字符
     *
     * @return
     */
    public boolean hasUnsafeChar() {
        return this.hasUnsafeChar;
    }

    public void replaceUnsafeChar(final String logInfo, HttpServletRequest request) {
        ServletInputStream servletInputStream = null;
        BufferedReader reqBufferedReader = null;

        try {
            // String requestContentType = request.getContentType();
            // String requestContentTypeLower = requestContentType == null ? null : requestContentType.toLowerCase();
            String requestCharacterEncoding = request.getCharacterEncoding();

            String usedCharset = requestCharacterEncoding != null ? requestCharacterEncoding : "UTF-8";

            Map<String, String[]> reqParameterMap = request.getParameterMap();

            // begin 解析requestParameterMap对象
            Map<String, String[]> paramMapNew = new HashMap<>();

            if (reqParameterMap != null && reqParameterMap.size() > 0) {
                Iterator<Map.Entry<String, String[]>> itParameterMap = reqParameterMap.entrySet().iterator();
                Map.Entry<String, String[]> entryParameterMap = null;
                String strKey = null;
                String[] arrValue = null;

                String newKey = null;
                String[] newArrValue = null;

                while (itParameterMap.hasNext()) {
                    entryParameterMap = itParameterMap.next();
                    strKey = entryParameterMap.getKey();
                    arrValue = entryParameterMap.getValue();

                    if (strKey != null) {
                        newKey = StringUtil.replaceUnsafeCharWithSql(strKey);
                    }

                    if (arrValue != null && arrValue.length > 0) {
                        newArrValue = new String[arrValue.length];

                        for (int aaa = 0; aaa < arrValue.length; aaa++) {
                            newArrValue[aaa] = StringUtil.replaceUnsafeCharWithSql(arrValue[aaa]);
                        }
                    }

                    paramMapNew.put(newKey, newArrValue);
                }
            }

            this.parameterMap = paramMapNew;
            this.parameterMapWithTable = new Hashtable<>(paramMapNew);
            // end 解析requestParameterMap对象

            // begin 读取RequestBody
            if (this.needUsedServletInputStream) {
                servletInputStream = request.getInputStream();

                String strSrcContent = "";
                char[] arrReadData = new char[1000];
                int readLen = 0;

                reqBufferedReader = new BufferedReader(new InputStreamReader(servletInputStream, usedCharset));

                while ((readLen = reqBufferedReader.read(arrReadData)) != -1) {
                    strSrcContent += new String(arrReadData, 0, readLen);
                }

                String strFormatContent = strSrcContent;

                if (!StringUtil.isNullOrSpace(strSrcContent)) {
                    try {
                        Object objReqJsonContent = JacksonUtil.readValue(strSrcContent, Object.class);
                        Object objFormat = formatObject(objReqJsonContent);
                        strFormatContent = JacksonUtil.writeValueAsString(objFormat);
                    } catch (Exception e) {
                        LOGGER.error("error;", e);
                        throw e;
                    }
                }

                requestBody = strFormatContent;
                LOGGER.info("strSrcContent:{};strFormatContent:{};", strSrcContent, strFormatContent);

                this.inputStream = new CharSafeServletInputStream(new ByteArrayInputStream(requestBody.getBytes(usedCharset)));
            }
            // end 读取RequestBody
        } catch (Exception e) {
            LOGGER.error("error", e);
            throw new RuntimeException(e);
        } finally {
            IOUtil.close(reqBufferedReader);
            IOUtil.close(servletInputStream);
        }
    }

    public void onlyCheckUnsafeChar(final String logInfo, HttpServletRequest request) {
        ServletInputStream servletInputStream = null;
        BufferedReader reqBufferedReader = null;

        try {
            // String requestContentType = request.getContentType();
            // String requestContentTypeLower = requestContentType == null ? null : requestContentType.toLowerCase();
            String requestCharacterEncoding = request.getCharacterEncoding();

            String usedCharset = requestCharacterEncoding != null ? requestCharacterEncoding : "UTF-8";

            // begin 解析requestParameterMap对象
            Map<String, String[]> reqParameterMap = request.getParameterMap();

            if (reqParameterMap != null && reqParameterMap.size() > 0) {
                Iterator<Map.Entry<String, String[]>> itParameterMap = reqParameterMap.entrySet().iterator();
                Map.Entry<String, String[]> entryParameterMap = null;
                String strKey = null;
                String[] arrValue = null;
                boolean hasUnsafeCharLocal = false;

                while (itParameterMap.hasNext()) {
                    entryParameterMap = itParameterMap.next();
                    strKey = entryParameterMap.getKey();
                    arrValue = entryParameterMap.getValue();

                    if (strKey != null) {
                        hasUnsafeCharLocal = StringUtil.hasUnsafeCharWithSql(logInfo, strKey);

                        if (hasUnsafeCharLocal) {
                            LOGGER.error("{}hasUnsafeCharLocal:{};strKey:{};"
                                    , logInfo
                                    , hasUnsafeCharLocal, strKey
                            );

                            this.hasUnsafeChar = hasUnsafeCharLocal;
                            return;
                        }
                    }

                    if (arrValue != null && arrValue.length > 0) {
                        String arrValueItem = null;

                        for (int aaa = 0; aaa < arrValue.length; aaa++) {
                            arrValueItem = arrValue[aaa];
                            hasUnsafeCharLocal = StringUtil.hasUnsafeCharWithSql(logInfo, arrValueItem);

                            if (hasUnsafeCharLocal) {
                                LOGGER.error("{}hasUnsafeCharLocal:{};arrValueItem:{};"
                                        , logInfo
                                        , hasUnsafeCharLocal, arrValueItem
                                );

                                this.hasUnsafeChar = hasUnsafeCharLocal;
                                return;
                            }
                        }
                    }
                }
            }

            this.parameterMap = reqParameterMap;
            this.parameterMapWithTable = reqParameterMap == null ? new Hashtable<>() : new Hashtable<>(reqParameterMap);
            // end 解析requestParameterMap对象

            // begin 读取RequestBody
            if (this.needUsedServletInputStream) {
                servletInputStream = request.getInputStream();

                String strSrcContent = "";
                char[] arrReadData = new char[1000];
                int readLen = 0;

                reqBufferedReader = new BufferedReader(new InputStreamReader(servletInputStream, usedCharset));

                while ((readLen = reqBufferedReader.read(arrReadData)) != -1) {
                    strSrcContent += new String(arrReadData, 0, readLen);
                }

                String strFormatContent = strSrcContent;

                if (!StringUtil.isNullOrSpace(strSrcContent)) {
                    try {
                        boolean hasUnsafeCharLocal = false;
                        Object objReqJsonContent = JacksonUtil.readValue(strSrcContent, Object.class);
                        hasUnsafeCharLocal = hasUnsafeCharWithRequestBody(logInfo, objReqJsonContent);

                        if (hasUnsafeCharLocal) {
                            LOGGER.error("{}strSrcContent:{};"
                                    , logInfo
                                    , strSrcContent
                            );

                            this.hasUnsafeChar = hasUnsafeCharLocal;
                            return;
                        }
                    } catch (Exception e) {
                        LOGGER.error("{}error;strSrcContent:{};"
                                , logInfo
                                , strSrcContent
                                , e
                        );

                        throw e;
                    }
                }

                this.requestBody = strFormatContent;
                this.inputStream = new CharSafeServletInputStream(new ByteArrayInputStream(requestBody.getBytes(usedCharset)));
            }
            // end 读取RequestBody
        } catch (Exception e) {
            LOGGER.error("{}error", logInfo, e);
            throw new RuntimeException(e);
        } finally {
            IOUtil.close(reqBufferedReader);
            IOUtil.close(servletInputStream);
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (this.inputStream != null) {
            return this.inputStream;
        }

        return super.getInputStream();
    }

    @Override
    public String getParameter(String name) {
        if (this.parameterMap != null) {
            String[] arrValue = this.parameterMap.get(name);

            if (arrValue != null && arrValue.length > 0) {
                return arrValue[0];
            }

            return null;
        }

        return super.getParameter(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        if (this.parameterMap != null) {
            return this.parameterMap;
        }

        return super.getParameterMap();
    }

    @Override
    public Enumeration<String> getParameterNames() {
        if (this.parameterMapWithTable != null) {
            return this.parameterMapWithTable.keys();
        }

        return super.getParameterNames();
    }

    @Override
    public String[] getParameterValues(String name) {
        if (this.parameterMap != null) {
            return this.parameterMap.get(name);
        }

        return super.getParameterValues(name);
    }

    private Object formatObject(Object reqObj) {
        if (reqObj instanceof String) {
            return StringUtil.replaceUnsafeCharWithSql((String) reqObj);
        }

        if (reqObj instanceof List) {
            List usedList = (List) reqObj;
            List newList = new ArrayList();

            if (usedList.size() > 0) {
                for (Object usedListItem : usedList) {
                    newList.add(formatObject(usedListItem));
                }
            }

            return newList;
        }

        if (reqObj instanceof Map) {
            Map mapReqJsonContent = (Map) reqObj;
            Map newMap = new HashMap();

            if (mapReqJsonContent.size() > 0) {
                Iterator<Map.Entry> itReqJsonContent = mapReqJsonContent.entrySet().iterator();
                Map.Entry entryReqJsonContent = null;
                Object entryReqJsonContentKey = null;
                Object entryReqJsonContentValue = null;
                Object newKey = null;
                Object newValue = null;

                while (itReqJsonContent.hasNext()) {
                    entryReqJsonContent = itReqJsonContent.next();
                    entryReqJsonContentKey = entryReqJsonContent.getKey();
                    entryReqJsonContentValue = entryReqJsonContent.getValue();

                    newKey = formatObject(entryReqJsonContentKey);
                    newValue = formatObject(entryReqJsonContentValue);

                    newMap.put(newKey, newValue);
                }
            }

            return newMap;
        }

        return reqObj;
    }

    private boolean hasUnsafeCharWithRequestBody(final String logInfo, Object reqObj) {
        if (reqObj == null) {
            return false;
        }

        if (reqObj instanceof String) {
            boolean hasUnsafeChar = StringUtil.hasUnsafeCharWithSql(logInfo, (String) reqObj);

            if (hasUnsafeChar) {
                LOGGER.error("{}hasUnsafeChar:{};reqObj:{};"
                        , logInfo
                        , hasUnsafeChar, reqObj
                );
            }

            return hasUnsafeChar;
        }

        if (reqObj instanceof List) {
            List usedList = (List) reqObj;
            boolean hasUnsafeChar = false;

            if (usedList.size() > 0) {
                for (Object usedListItem : usedList) {
                    hasUnsafeChar = hasUnsafeCharWithRequestBody(logInfo, usedListItem);

                    if (hasUnsafeChar) {
                        LOGGER.error("{}hasUnsafeChar:{};usedListItem:{};"
                                , logInfo
                                , hasUnsafeChar, usedListItem
                        );

                        return hasUnsafeChar;
                    }
                }
            }

            return hasUnsafeChar;
        }

        if (reqObj instanceof Map) {
            Map mapReqJsonContent = (Map) reqObj;
            boolean hasUnsafeChar = false;

            if (mapReqJsonContent.size() > 0) {
                Iterator<Map.Entry> itReqJsonContent = mapReqJsonContent.entrySet().iterator();
                Map.Entry entryReqJsonContent = null;
                Object entryReqJsonContentKey = null;
                Object entryReqJsonContentValue = null;

                while (itReqJsonContent.hasNext()) {
                    entryReqJsonContent = itReqJsonContent.next();
                    entryReqJsonContentKey = entryReqJsonContent.getKey();
                    entryReqJsonContentValue = entryReqJsonContent.getValue();

                    hasUnsafeChar = hasUnsafeCharWithRequestBody(logInfo, entryReqJsonContentKey);

                    if (hasUnsafeChar) {
                        LOGGER.error("{}hasUnsafeChar:{};entryReqJsonContentKey:{};"
                                , logInfo
                                , hasUnsafeChar, entryReqJsonContentKey
                        );

                        return hasUnsafeChar;
                    }

                    hasUnsafeChar = hasUnsafeCharWithRequestBody(logInfo, entryReqJsonContentValue);

                    if (hasUnsafeChar) {
                        LOGGER.error("{}hasUnsafeChar:{};entryReqJsonContentValue:{};"
                                , logInfo
                                , hasUnsafeChar, entryReqJsonContentValue
                        );

                        return hasUnsafeChar;
                    }
                }
            }

            return hasUnsafeChar;
        }

        return false;
    }

    private boolean needUsedServletInputStream(HttpServletRequest request) {
        String requestContentType = request.getContentType();
        String requestContentTypeLower = requestContentType == null ? null : requestContentType.toLowerCase();

        if (requestContentTypeLower != null
                && (
                requestContentTypeLower.equals("application/json".toLowerCase(Locale.ENGLISH))
                        || requestContentTypeLower.startsWith("application/json;".toLowerCase(Locale.ENGLISH))
                        || requestContentTypeLower.equals("application/xml".toLowerCase(Locale.ENGLISH))
                        || requestContentTypeLower.startsWith("application/xml;".toLowerCase(Locale.ENGLISH))
                        || requestContentTypeLower.equals("application/javascript".toLowerCase(Locale.ENGLISH))
                        || requestContentTypeLower.startsWith("application/javascript;".toLowerCase(Locale.ENGLISH))
                        || requestContentTypeLower.startsWith("text/".toLowerCase(Locale.ENGLISH))
        )
        ) {
            return true;
        }

        return false;
    }

}
